﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Text.RegularExpressions;
using MinhUtils;
using MangaGrr.DomainModel;

namespace MangaGrr
{
    class MGRUtil
    {
        private static readonly string DEFAULT_IMAGE_EXTENSION = ".jpg";

        internal static bool Download(MangaPage page, string folderPath)
        {
            String filePath = GenerateFilePath(page, folderPath);
            return Download(page.ImageURL, filePath);
        }

        internal static bool Download(string url, string filePath)
        {
            try
            {
                NetUtil.DownloadFile(url, filePath);
            }
            catch (Exception ex)
            {
                if (ex.Message.Contains("503") //Server Unavailable
                    || ex.Message.Contains("The operation has timed out") //The operation has timed out
                    || ex.Message.Contains("Unable to connect to the remote server") //Unable to connect to the remote server
                    || ex.Message.Contains("The remote name could not be resolved") //The remote name could not be resolved
                    )
                {
                    return false; //do not write to log BUT try again
                }
                else if (ex.Message.Contains("404")) //File not Found
                {
                    return true; //do not write to log AND do not try again
                }
                else
                {
                    //write to log
                    StringBuilder sbLogMessage = new StringBuilder();
                    sbLogMessage.Append("EXCEPTION: " + ex.Message + ". ");
                    sbLogMessage.Append("URL: " + url + ". ");
                    sbLogMessage.Append("SAVEAS: " + filePath + ".");
                    WriteLog(sbLogMessage.ToString());
                }

                //try again
                return false;
            }

            return true;
        }

        static System.IO.StreamWriter logWriter;
        internal static void WriteLog(string logMessage)
        {
            //http://msdn.microsoft.com/en-us/library/3zc0w663.aspx
            try
            {
                using (logWriter = System.IO.File.AppendText("log.txt"))
                {
                    lock (logWriter)
                    {
                        logWriter.Write("\r\nLog Entry : ");
                        logWriter.WriteLine("{0} {1}", DateTime.Now.ToLongTimeString(), DateTime.Now.ToLongDateString());
                        //logWriter.WriteLine("  :");
                        //logWriter.WriteLine("  :{0}", logMessage);
                        logWriter.WriteLine(":{0}", logMessage);
                        logWriter.WriteLine("-------------------------------");
                        // Update the underlying file.
                        logWriter.Flush();

                        // Close the writer and underlying file.
                        logWriter.Close();
                    }
                }
            }
            catch (System.IO.IOException)
            {
                System.Threading.Thread.Sleep(1000);
                WriteLog(logMessage);
            }
        }

        /// <summary>
        /// Generate full path to save a page and create all sub-folders if necessary
        /// </summary>
        private static string GenerateFilePath(MangaPage page, string baseFolder)
        {
            string fileName = FileUtil.GetFileName(page.ImageURL);

            //if file name is too long then use page number instead
            if (fileName.Length > MangaGrr.Properties.Settings.Default.FileNameLengthLimit)
                fileName = PadZeros(page.Number) + GetFileExtension(page.ImageURL);

            //if file has no extension
            if (string.IsNullOrEmpty(FileUtil.GetFileExtension(fileName)))
                fileName += DEFAULT_IMAGE_EXTENSION;

            //if file name is only one or two digits (e.g 1.jpg, 11.jpg) then add extra zeros
            if (Regex.IsMatch(fileName, "^\\d{1,2}\\.\\w{3}"))
                fileName = PadZeros(StringUtil.PickInteger(fileName)) + GetFileExtension(fileName);

            string mangaName = FileUtil.EliminateInvalidCharacters(page.Chapter.Manga.Name);
            string chapterNumberAndName = FileUtil.EliminateInvalidCharacters(page.Chapter.DisplayName);

            string folderPath = baseFolder + @"/" + mangaName + @"/" + mangaName + "_" + chapterNumberAndName;
            //e.g. C:/MangaFolder/MangaName/MangaName_001 Chapter Name/image001.jpg

            //create folders and subfolders if necessary
            FileUtil.CreateFolder(folderPath);

            return folderPath + @"/" + fileName;
        }

        /// <summary>
        /// Get file extension from an Uri. If extension not found, use default image extension
        /// </summary>
        public static string GetFileExtension(string uri)
        {
            string ext = FileUtil.GetFileExtension(uri);

            if (string.IsNullOrEmpty(ext))
                ext = DEFAULT_IMAGE_EXTENSION;

            return ext;
        }

        /// <summary>
        /// Add extra zeros to the given integer and return as string. Maximum of 2 zeros can be added. E.g 1->"001", 11->"011"
        /// </summary>
        internal static string PadZeros(int value)
        {
            return StringUtil.PadZeros(value, 3);
        }

        /// <summary>
        /// Add extra zeros to the given double value and return as string. Maximum of 2 zeros can be added. E.g 1.23->"001.23", 11.112->"011.112"
        /// </summary>
        internal static string PadZeros(double value)
        {
            return StringUtil.PadZeros(value, 3);
        }

        /// <summary>
        /// Try downloading a Page. If fail then retry after each given interval
        /// </summary>
        internal static void TryDownload(MangaPage page, string folderPath, int interval)
        {
            bool result = Download(page, folderPath);

            while (result == false)
            {
                System.Threading.Thread.Sleep(interval);
                result = Download(page, folderPath);
            }
        }

        internal static bool IsValidFolder(string folderPath)
        {
            try
            {
                FileUtil.CreateFolder(folderPath);
                return true;
            }
            catch
            {
                return false;
            }
        }

        /// <summary>
        /// Check if an Chapter/Page's URL is valid
        /// </summary>
        internal static bool IsValidURL(string url)
        {
            //is "/"
            if (url.Equals("/"))
                return false;

            //does not start with "http://"
            if (url.StartsWith("http://") == false)
                return false;

            //is like http://somesite.net/ or http://somesite.net (the site's address)
            if (StringUtil.CountOccurences(url, "/") < 3)
                return false;
            else if (url.EndsWith("/") && StringUtil.CountOccurences(url, "/") == 3)
                return false;

            return true;
        }

        /// <summary>
        /// http://social.msdn.microsoft.com/forums/en-US/netfxnetcom/thread/ff098248-551c-4da9-8ba5-358a9f8ccc57/
        /// </summary>
        internal static bool AllowUnsafeHeaderParsing()
        {
            //Get the assembly that contains the internal class
            System.Reflection.Assembly aNetAssembly = System.Reflection.Assembly.GetAssembly(typeof(System.Net.Configuration.SettingsSection));
            if (aNetAssembly != null)
            {
                //Use the assembly in order to get the internal type for the internal class
                Type aSettingsType = aNetAssembly.GetType("System.Net.Configuration.SettingsSectionInternal");
                if (aSettingsType != null)
                {
                    //Use the internal static property to get an instance of the internal settings class.
                    //If the static instance isn't created allready the property will create it for us.
                    object anInstance = aSettingsType.InvokeMember("Section", System.Reflection.BindingFlags.Static | System.Reflection.BindingFlags.GetProperty | System.Reflection.BindingFlags.NonPublic, null, null, new object[] { });

                    if (anInstance != null)
                    {
                        //Locate the private bool field that tells the framework is unsafe header parsing should be allowed or not
                        System.Reflection.FieldInfo aUseUnsafeHeaderParsing = aSettingsType.GetField("useUnsafeHeaderParsing", System.Reflection.BindingFlags.NonPublic | System.Reflection.BindingFlags.Instance);
                        if (aUseUnsafeHeaderParsing != null)
                        {
                            aUseUnsafeHeaderParsing.SetValue(anInstance, true);
                            return true;
                        }
                    }
                }
            }
            return false;
        }

        /// <summary>
        /// Check if a MangaSite is special
        /// </summary>
        internal static bool IsZenManga(MangaSite mangaSite)
        {
            return mangaSite.Name.Equals("ZenManga");
        }

        internal static string PadZeros(string s)
        {
            return StringUtil.PadZeros(s, 3);
        }
    }
}
